package com.fuhailiu.opengl.widget;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

import android.content.Context;
import android.opengl.GLES30;
import android.os.SystemClock;

import com.fuhailiu.opengl.utils.GlUtil;
import com.fuhailiu.opengl.utils.LogUtil;
import com.fuhailiu.opengl.utils.MatrixUtil;
import com.fuhailiu.opengl.utils.ShaderUtil;
import com.fuhailiu.opengl.view.GLView;

public class GLFBO extends GLView {

	private final String TAG = getClass().getSimpleName();

	private int mSurfaceWidth, mSurfaceHeight;

	public GLFBO(Context context, int surfaceWidth, int surfaceHeight) {
		super(context);
		mSurfaceWidth = surfaceWidth;
		mSurfaceHeight = surfaceHeight;
		initOffScreenShader();
		initOnScreenShader();
		initFBO();
	}

	private static final String OFF_VERTEX_SHADER = 
			"uniform mat4 uMVPMatrix; //总变换矩阵\n" + 
			"attribute vec3 aPosition; //顶点位置\n" + 
			"attribute vec4 aColor; //顶点颜色\n" + 
			"varying vec4 vColor; //用于传递给片元着色器的变量\n" + 
			"void main() { \n" + 
			"	gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置\n" + 
			"	vColor = aColor; //将接收的颜色传递给片元着色器 \n" + 
			"}\n";

	private static final String OFF_FRAGMENT_SHADER = 
			"precision mediump float;\n" + 
			"varying vec4 vColor; //接收从顶点着色器过来的参数\n" + 
			"void main() { \n" + 
			"	gl_FragColor = vColor;\n" + 
			"}\n";

	// 自定义渲染管线程序id
	private int handleOffProgram;
	// 顶点位置属性引用id
	private int handleOffPosition;
	// 顶点颜色属性引用id
	private int handleOffColor;
	// 总变换矩阵引用id
	private int handleOffMVPMatrix;

	private void initOffScreenShader() {
		LogUtil.LogD(TAG, "initOffScreenShader   <---");

		// 基于顶点着色器与片元着色器创建程序
		handleOffProgram = ShaderUtil.createProgram(OFF_VERTEX_SHADER, OFF_FRAGMENT_SHADER);
		// 获取程序中顶点位置属性引用id
		handleOffPosition = GLES30.glGetAttribLocation(handleOffProgram, "aPosition");
		// 获取程序中顶点颜色属性引用id
		handleOffColor = GLES30.glGetAttribLocation(handleOffProgram, "aColor");
		// 获取程序中总变换矩阵引用id
		handleOffMVPMatrix = GLES30.glGetUniformLocation(handleOffProgram, "uMVPMatrix");

		LogUtil.LogD(TAG, "initOffScreenShader   --->");
	}

	private static final String ON_ERTEX_SHADER = 
			"uniform mat4 uMVPMatrix; //总变换矩阵\n" + 
			"attribute vec3 aPosition; //顶点位置\n" + 
			"attribute vec4 aTextureCoord;\n" + 
			"varying vec2 vTextureCoord;\n" + 
			"void main() { \n" + 
			"	gl_Position = uMVPMatrix * vec4(aPosition,1); //根据总变换矩阵计算此次绘制此顶点位置\n" + 
			"	vTextureCoord = aTextureCoord.xy;\n" + 
			"}\n";

	private static final String ON_FRAGMENT_SHADER = 
			"precision mediump float;\n" + 
			"varying vec2 vTextureCoord;\n" + 
			"uniform sampler2D uTexture;" +
			"void main() { \n" + 
			"	gl_FragColor = texture2D(uTexture, vTextureCoord);\n" + 
			"}\n";

	// 自定义渲染管线程序id
	private int handleOnProgram;
	// 顶点位置属性引用id
	private int handleOnPosition;
	// 总变换矩阵引用id
	private int handleOnMVPMatrix;
	// 纹理坐标引用id
	private int handleOnTextureCoord;

	private void initOnScreenShader() {
		LogUtil.LogD(TAG, "initOnScreenShader   <---");

		// 基于顶点着色器与片元着色器创建程序
		handleOnProgram = ShaderUtil.createProgram(ON_ERTEX_SHADER, ON_FRAGMENT_SHADER);
		// 获取程序中顶点位置属性引用id
		handleOnPosition = GLES30.glGetAttribLocation(handleOnProgram, "aPosition");
		// 获取程序中总变换矩阵引用id
		handleOnMVPMatrix = GLES30.glGetUniformLocation(handleOnProgram, "uMVPMatrix");
		// 获取纹理坐标引用id
		handleOnTextureCoord = GLES30.glGetAttribLocation(handleOnProgram, "aTextureCoord");

		LogUtil.LogD(TAG, "initOnScreenShader   --->");
	}

	private int mFrameBufferWidth = 256;
	private int mFrameBufferHeight = 256;

	private int mTargetTexture;
	private int mFrameBuffer;
	private int mRenderBuffer;

	private Triangle mTriangle;
	private Cube mCube;

	private void initFBO() {
		LogUtil.LogD(TAG, "initFBO   <---");

		mTargetTexture = createTargetTexture(mFrameBufferWidth, mFrameBufferHeight);
		mFrameBuffer = createFrameBuffer(mFrameBufferWidth, mFrameBufferHeight, mTargetTexture);

		mCube = new Cube();
		mTriangle = new Triangle();

		LogUtil.LogD(TAG, "initFBO   --->");
	}

	private int createTargetTexture(int width, int height) {
		int texture;
		int[] textures = new int[1];

		// 创建纹理
		GLES30.glGenTextures(1, textures, 0);
		texture = textures[0];

		// 绑定纹理
		GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texture);

		// 分配空间
		GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, width, height, 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);

		// 设置参数
		GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
		GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
		GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT);
		GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_REPEAT);

		// 取消绑定
		GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);

		// 检查错误
		GlUtil.checkGlError("createTargetTexture");

		return texture;
	}

	private int createFrameBuffer(int width, int height, int targetTextureId) {

		int[] framebuffers = new int[1];

		// 创建FBO
		GLES30.glGenFramebuffers(1, framebuffers, 0);
		int framebuffer = framebuffers[0];

		// 绑定FBO
		GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, framebuffer);

		// 创建RBO以供FBO使用，RBO即渲染缓冲区，其实就是一个用来支持离屏渲染的缓冲区。
		// 通常是帧缓冲区的一部份，一般不具有纹理格式。常见的模版缓冲和深度缓冲就是这样一类对像。
		// 为FBO指定一个渲染缓冲区。这样，当渲染的时候，便把这个渲染缓冲区作为FBO的一个深度缓存
		int depthbuffer;
		int[] renderbuffers = new int[1];
		GLES30.glGenRenderbuffers(1, renderbuffers, 0);
		mRenderBuffer = depthbuffer = renderbuffers[0];

		// 绑定RBO
		GLES30.glBindRenderbuffer(GLES30.GL_RENDERBUFFER, depthbuffer);
		// 生成的RBO并不会自动分配内存空间。因此需要给它分配指定大小的内存空间，在这里，我们分配一个固定大小的深度缓存空间。
		GLES30.glRenderbufferStorage(GLES30.GL_RENDERBUFFER, GLES30.GL_DEPTH_COMPONENT16, width, height);
		// RBO与FBO对像绑定
		GLES30.glFramebufferRenderbuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_DEPTH_ATTACHMENT, GLES30.GL_RENDERBUFFER, depthbuffer);
		// Texture2D与FBO对像绑定，即渲染到纹理：用纹理作为颜色缓冲区而不是使用RenderBuffer作为颜色缓冲区。
		// 如果使用RenderBuffer作为颜色缓冲区，就是通常所说的离屏渲染
		GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, targetTextureId, 0);

		// 检查FBO状态
		int status = GLES30.glCheckFramebufferStatus(GLES30.GL_FRAMEBUFFER);
		if (status != GLES30.GL_FRAMEBUFFER_COMPLETE) {
			throw new RuntimeException("Framebuffer is not complete: " + Integer.toHexString(status));
		}

		// 解除FBO绑定
		GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);

		// 检查错误
		GlUtil.checkGlError("createFrameBuffer");

		return framebuffer;
	}

	@Override
	public void release() {
		LogUtil.LogD(TAG, "release   <---");

		super.release();
		GLES30.glDeleteTextures(1, new int[] { mTargetTexture }, 0);
		GLES30.glDeleteRenderbuffers(1, new int[] { mRenderBuffer }, 0);
		GLES30.glDeleteFramebuffers(1, new int[] { mFrameBuffer }, 0);

		LogUtil.LogD(TAG, "release   --->");
	}

	private boolean mbOffScreen = true;

	public void setOffScreen(boolean bOffScreen) {
		LogUtil.LogD(TAG, "setDebugMode   <---");
		LogUtil.LogD(TAG, "setDebugMode   bOn = " + bOffScreen);

		mbOffScreen = bOffScreen;

		LogUtil.LogD(TAG, "setDebugMode   --->");
	}

	public void draw(GL10 gl) {
		if (mbOffScreen) {
			GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
			drawOffscreenImage(mSurfaceWidth, mSurfaceHeight);
		} else {
			GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBuffer);
			drawOffscreenImage(mFrameBufferWidth, mFrameBufferHeight);
			GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, 0);
			drawOnscreen(mSurfaceWidth, mSurfaceHeight);
		}
	}

	private float mAngle;

	private void drawOffscreenImage(int width, int height) {
		// 背景颜色
		GLES30.glClearColor(0, 0.5f, 1, 0);
		GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);

		// 设置视窗大小及位置
		GLES30.glViewport(0, 0, width, height);
		// 设置摄像机位置
		MatrixUtil.setLookAt(0, 0, -7, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
		// 设置透视投影
		float ratio = 1.0f * width / height;
		MatrixUtil.setProjectFrustum(-ratio, ratio, -1, 1, 1, 13);

		// 保存矩阵
		MatrixUtil.pushMatrix();

		// 画立方体
		MatrixUtil.translate(0, 0, -3.0f);
		MatrixUtil.rotate(mAngle, 0, 1, 0);
		MatrixUtil.rotate(mAngle * 0.25f, 1, 0, 0);
		mCube.draw();

		// 画立方体
		MatrixUtil.rotate(mAngle * 2.0f, 0, 1, 1);
		MatrixUtil.translate(0.5f, 0.5f, 0.5f);
		mCube.draw();

		// 恢复矩阵
		MatrixUtil.popMatrix();

		mAngle += 1.2f;
	}

	private void drawOnscreen(int width, int height) {
		// 背景颜色
		GLES30.glClearColor(0, 0, 1, 0);
		GLES30.glClear(GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);

		// 设置视窗大小及位置
		GLES30.glViewport(0, 0, width, height);
		// 设置摄像机位置
		MatrixUtil.setLookAt(0, 0, -5, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
		// 设置透视投影
		float ratio = 1.0f * width / height;
		MatrixUtil.setProjectFrustum(-ratio, ratio, -1, 1, 3, 7);

		// 绑定纹理，将FBO作为纹理读取
		GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
		GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTargetTexture);

		// 保存矩阵
		MatrixUtil.pushMatrix();

		// 计算角度
		long time = SystemClock.uptimeMillis() % 4000L;
		float angle = 0.090f * ((int) time);
		MatrixUtil.rotate(angle, 0, 0, 1.0f);

		// 画三角形
		mTriangle.draw();

		// 恢复矩阵
		MatrixUtil.popMatrix();

		// 取消绑定
		GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0);
	}

	class Cube {

		private final int one = 0x10000;

		private final int vertices[] = { 
				-one, -one, -one, 
				one, -one, -one, 
				one, one, -one, 
				-one, one, -one, 
				-one, -one, one, 
				one, -one, one, 
				one, one, one, 
				-one, one, one, 
		};

		private final int colors[] = { 
				0, 0, 0, one, 
				one, 0, 0, one, 
				one, one, 0, one, 
				0, one, 0, one, 
				0, 0, one, one, 
				one, 0, one, one, 
				one, one, one, one, 
				0, one, one, one, 
		};

		private final byte indices[] = { 
				0, 4, 5, 0, 5, 1, 
				1, 5, 6, 1, 6, 2, 
				2, 6, 7, 2, 7, 3, 
				3, 7, 4, 3, 4, 0, 
				4, 7, 6, 4, 6, 5, 
				3, 0, 1, 3, 1, 2 
		};

		private IntBuffer mVertexBuffer;
		private IntBuffer mColorBuffer;
		private ByteBuffer mIndexBuffer;

		public Cube() {
			mVertexBuffer = GlUtil.createIntBuffer(vertices);
			mColorBuffer = GlUtil.createIntBuffer(colors);
			mIndexBuffer = GlUtil.createByteBuffer(indices);
		}

		public void draw() {
			// 指定顺时针为“前面”
			GLES30.glFrontFace(GLES30.GL_CW);
			// 启用裁剪
			GLES30.glEnable(GLES30.GL_CULL_FACE);
			// 裁剪“后面”
			GLES30.glCullFace(GLES30.GL_BACK);

			// 使用shader程序
			GLES30.glUseProgram(handleOffProgram);

			// 指定顶点位置数据
			GLES30.glVertexAttribPointer(handleOffPosition, 3, GLES30.GL_FIXED, false, 3 * GlUtil.SIZE_OF_INT, mVertexBuffer);
			// 指定顶点颜色数据
			GLES30.glVertexAttribPointer(handleOffColor, 4, GLES30.GL_FIXED, false, 4 * GlUtil.SIZE_OF_INT, mColorBuffer);
			// 指定变换矩阵
			GLES30.glUniformMatrix4fv(handleOffMVPMatrix, 1, false, MatrixUtil.getMVPMatrix(), 0);

			// 启用顶点位置数组
			GLES30.glEnableVertexAttribArray(handleOffPosition);
			// 启用顶点颜色数组
			GLES30.glEnableVertexAttribArray(handleOffColor);

			// 绘制三角形
			GLES30.glDrawElements(GLES30.GL_TRIANGLES, 36, GLES30.GL_UNSIGNED_BYTE, mIndexBuffer);

			// 禁用顶点位置数组
			GLES30.glDisableVertexAttribArray(handleOffColor);
			// 禁用顶点颜色数组
			GLES30.glDisableVertexAttribArray(handleOffPosition);

			// 禁用背面裁剪
			GLES30.glDisable(GLES30.GL_CULL_FACE);
		}
	}

	class Triangle {
		private final float[] coords = { 
				-0.5f, -0.25f, 0, // left bottom
				0.5f, -0.25f, 0, // right bottom
				0.0f, 0.559016994f, 0, // middle top
		};

		private static final float scale = 2.0f;
		private final int VERTEX_COUNT = 3;

		private FloatBuffer mBufferVertex;
		private FloatBuffer mBufferTextCoord;
		private ShortBuffer mBufferIndex;

		public Triangle() {
			// 顶点坐标
			ByteBuffer vbb = ByteBuffer.allocateDirect(VERTEX_COUNT * 3 * GlUtil.SIZE_OF_FLOAT);
			vbb.order(ByteOrder.nativeOrder());
			mBufferVertex = vbb.asFloatBuffer();
			for (int i = 0; i < VERTEX_COUNT; i++) {
				for (int j = 0; j < 3; j++) {
					mBufferVertex.put(coords[i * 3 + j] * scale);
				}
			}
			mBufferVertex.position(0);
			// 纹理坐标
			ByteBuffer tbb = ByteBuffer.allocateDirect(VERTEX_COUNT * 2 * 4);
			tbb.order(ByteOrder.nativeOrder());
			mBufferTextCoord = tbb.asFloatBuffer();
			for (int i = 0; i < VERTEX_COUNT; i++) {
				for (int j = 0; j < 2; j++) {
					mBufferTextCoord.put(coords[i * 3 + j] * 2.0f + 0.5f);
				}
			}
			mBufferTextCoord.position(0);
			// 顶点索引
			ByteBuffer ibb = ByteBuffer.allocateDirect(VERTEX_COUNT * GlUtil.SIZE_OF_SHORT);
			ibb.order(ByteOrder.nativeOrder());
			mBufferIndex = ibb.asShortBuffer();
			for (int i = 0; i < VERTEX_COUNT; i++) {
				mBufferIndex.put((short) i);
			}
			mBufferIndex.position(0);
		}

		public void draw() {
			// 指定顺时针为“前面”
			GLES30.glFrontFace(GLES30.GL_CW);
			// 启用裁剪
			GLES30.glEnable(GLES30.GL_CULL_FACE);
			// 裁剪“后面”
			GLES30.glCullFace(GLES30.GL_BACK);

			// 使用shader程序
			GLES30.glUseProgram(handleOnProgram);

			// 指定顶点位置数据
			GLES30.glVertexAttribPointer(handleOnPosition, 3, GLES30.GL_FLOAT, false, 3 * GlUtil.SIZE_OF_FLOAT, mBufferVertex);
			// 指定纹理坐标数据
			GLES30.glVertexAttribPointer(handleOnTextureCoord, 2, GLES30.GL_FLOAT, false, 2 * GlUtil.SIZE_OF_FLOAT, mBufferTextCoord);
			// 指定变换矩阵
			GLES30.glUniformMatrix4fv(handleOnMVPMatrix, 1, false, MatrixUtil.getMVPMatrix(), 0);

			// 启用顶点位置数组
			GLES30.glEnableVertexAttribArray(handleOnPosition);
			// 启用纹理坐标数组
			GLES30.glEnableVertexAttribArray(handleOnTextureCoord);

			// 绘制三角形
			GLES30.glDrawElements(GLES30.GL_TRIANGLE_STRIP, VERTEX_COUNT, GLES30.GL_UNSIGNED_SHORT, mBufferIndex);

			// 禁用顶点位置数组
			GLES30.glDisableVertexAttribArray(handleOnPosition);
			// 禁用纹理坐标数组
			GLES30.glDisableVertexAttribArray(handleOnTextureCoord);

			// 禁用背面裁剪
			GLES30.glDisable(GLES30.GL_CULL_FACE);
		}
	}

}
